﻿#region Using

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Vk.Methods.Common;
using Vk.Methods.Photos;

#endregion

namespace Vk.Methods.Notes
{
    /// <summary>
    ///     Заметки
    /// </summary>
    public class Notes
    {
        private readonly Api _api;

        public Notes(Api api)
        {
            _api = api;
        }

        /// <summary>
        ///     Создает новую заметку у текущего пользователя.
        /// </summary>
        /// <param name="title">Заголовок заметки.</param>
        /// <param name="text">Текст заметки.</param>
        /// <param name="privacyView">
        ///     Уровень доступа к заметке. Значения: 0 – все пользователи, 1 – только друзья, 2 – друзья и друзья
        ///     друзей, 3 - только пользователь.
        /// </param>
        /// <param name="privacyComment">
        ///     Уровень доступа к комментированию заметки. Значения: 0 – все пользователи, 1 – только
        ///     друзья, 2 – друзья и друзья друзей, 3 - только пользователь.
        /// </param>
        /// <returns>В случае успешного создания заметки метод возвратит идентификатор созданной заметки (nid).</returns>
        public async Task<int> Add(string title,
                                   string text,
                                   Privacy privacyView = null,
                                   Privacy privacyComment = null)
        {
            if (title == null)
            {
                throw new ArgumentNullException(nameof(title));
            }
            if (text == null)
            {
                throw new ArgumentNullException(nameof(text));
            }

            var query = new ApiQuery("notes.add")
                .Add("title", title)
                .Add("text", text)
                .Add("privacy_view", privacyView)
                .Add("privacy_comment", privacyComment);

            var result = await _api.Manager.Execute(query);

            return _api.Manager.MethodSuccessed && result.Int().HasValue ? result.Int().Value : -1;
        }

        /// <summary>
        ///     Добавляет новый комментарий к заметке.
        /// </summary>
        /// <param name="noteId">ID заметки, в которой нужно создать комментарий</param>
        /// <param name="message">Текст комментария (минимальная длина - 2 символа).</param>
        /// <param name="ownerId">Идентификатор пользователя, владельца заметки (по умолчанию - текущий пользователь).</param>
        /// <param name="replyTo">
        ///     ID пользователя, ответом на комментарий которого является добавляемый комментарий (не передаётся
        ///     если комментарий не является ответом).
        /// </param>
        /// <returns>Возвращает идентификатор созданного комментария (cid) или код ошибки. </returns>
        public async Task<int> CreateComment(int noteId, string message, int? ownerId = null, int? replyTo = null, string guid = null)
        {
            if (message == null)
            {
                throw new ArgumentNullException(nameof(message));
            }

            var query = new ApiQuery("notes.createComment")
                .Add("message", message)
                .Add("note_id", noteId)
                .Add("owner_id", ownerId)
                .Add("guid", guid)
                .Add("reply_to", replyTo);

            var result = await _api.Manager.Execute(query);

            return _api.Manager.MethodSuccessed && result.Int().HasValue ? result.Int().Value : -1;
        }

        /// <summary>
        ///     Удаляет заметку текущего пользователя.
        /// </summary>
        /// <param name="noteId">ID удаляемой заметки.</param>
        /// <returns>В случае успешного удаления заметки метод возвратит true. </returns>
        public async Task<bool> Delete(int noteId)
        {
            var query = new ApiQuery("notes.delete")
                .Add("note_id", noteId);

            var result = await _api.Manager.Execute(query);

            return _api.Manager.MethodSuccessed && result.Bool().Value;
        }

        /// <summary>
        ///     Удаляет комментарий.
        /// </summary>
        /// <param name="commentId">ID комментария, котороый нужно удалить</param>
        /// <param name="ownerId">Идентификатор пользователя, владельца заметки (по-умолчанию - текущий пользователь).</param>
        /// <returns>В случае успешного удаления комментария метод возвратит true. </returns>
        public async Task<bool> DeleteComment(int commentId, int? ownerId = null)
        {
            var query = new ApiQuery("notes.deleteComment")
                .Add("comment_id", commentId)
                .Add("owner_id", ownerId);

            var result = await _api.Manager.Execute(query);

            return _api.Manager.MethodSuccessed && result.Bool().Value;
        }

        /// <summary>
        ///     Редактирует заметку текущего пользователя.
        /// </summary>
        /// <param name="noteId">ID редактируемой заметки.</param>
        /// <param name="title">Заголовок заметки.</param>
        /// <param name="text">Текст заметки.</param>
        /// <param name="privacyView">
        ///     Уровень доступа к заметке. Значения: 0 – все пользователи, 1 – только друзья, 2 – друзья и друзья
        ///     друзей, 3 - только пользователь.
        /// </param>
        /// <param name="privacyComment">
        ///     уровень доступа к комментированию заметки. Значения: 0 – все пользователи, 1 – только
        ///     друзья, 2 – друзья и друзья друзей, 3 - только пользователь.
        /// </param>
        /// <returns>В случае успешного сохранения заметки метод возвратит true. </returns>
        public async Task<bool> Edit(int noteId,
                                     string title,
                                     string text,
                                     Privacy privacyView = null,
                                     Privacy privacyComment = null)
        {
            if (title == null)
            {
                throw new ArgumentNullException(nameof(title));
            }
            if (text == null)
            {
                throw new ArgumentNullException(nameof(text));
            }

            var query = new ApiQuery("notes.edit")
                .Add("note_id", noteId)
                .Add("title", title)
                .Add("text", text)
                .Add("privacy_view", privacyView)
                .Add("privacy_comment", privacyComment);

            var result = await _api.Manager.Execute(query);

            return _api.Manager.MethodSuccessed && result.Bool().Value;
        }

        /// <summary>
        ///     Редактирует указанный комментарий у заметки.
        /// </summary>
        /// <param name="commentId">ID комментария, котороый нужно отредактировать</param>
        /// <param name="message">Идентификатор пользователя, владельца заметки (по умолчанию - текущий пользователь).</param>
        /// <param name="ownerId">Новый текст комментария (минимальная длина - 2 символа).</param>
        /// <returns>В случае успешного сохранения комментария метод возвратит true. </returns>
        public async Task<bool> EditComment(int commentId, string message, int? ownerId = null)
        {
            if (message == null)
            {
                throw new ArgumentNullException(nameof(message));
            }

            var query = new ApiQuery("notes.editComment")
                .Add("message", message)
                .Add("comment_id", commentId)
                .Add("owner_id", ownerId);

            var result = await _api.Manager.Execute(query);

            return _api.Manager.MethodSuccessed && result.Bool().Value;
        }

        /// <summary>
        ///     Возвращает список заметок, созданных пользователем.
        /// </summary>
        /// <param name="userId">ID пользователя, заметки которого нужно вернуть. По умолчанию – id текущего пользователя.</param>
        /// <param name="noteIds">Перечисленные через запятую id заметок, входящие в выборку по userId.</param>
        /// <param name="sort">
        ///     Сортировка результатов (0 - по дате создания в порядке убывания, 1 - по дате создания в порядке
        ///     возрастания).
        /// </param>
        /// <param name="count">Количество сообщений, которое необходимо получить (но не более 100). По умолчанию выставляется 20.</param>
        /// <param name="offset">Смещение, необходимое для выборки определенного подмножества заметок.</param>
        public async Task<ListCount<Note>> Get(int? userId = null,
                                               IEnumerable<string> noteIds = null,
                                               SortOrder sort = null,
                                               int? count = null,
                                               int? offset = null)
        {
            var query = new ApiQuery("notes.get")
                .Add("user_id", userId)
                .Add("note_ids", noteIds)
                .Add("sort", sort?.StringValue)
                .Add("count", count)
                .Add("offset", offset);

            var result = await _api.Manager.Execute(query);

            if (_api.Manager.MethodSuccessed)
            {
                var nodes = result.SelectNodes("items/*");
                if (nodes != null && nodes.Any())
                    return new ListCount<Note>(result.Int("count").Value, nodes.Select(x => new Note(x)).ToList());
            }
            return null;
        }

        /// <summary>
        ///     Возвращает заметку по её id.
        /// </summary>
        /// <param name="noteId">ID запрашиваемой заметки.</param>
        /// <param name="ownerId">ID владельца заметки (по умолчанию используется id текущего пользователя)</param>
        /// <param name="needWiki">
        ///     Определяет, требуется ли в ответе wiki-представление заметки (работает, только если
        ///     запрашиваются заметки текущего пользователя)
        /// </param>
        public async Task<NoteExtended> GetById(int noteId, int? ownerId = null, bool? needWiki = null)
        {
            var query = new ApiQuery("notes.getById")
                .Add("note_id", noteId)
                .Add("owner_id", ownerId)
                .Add("need_wiki", needWiki);

            var result = await _api.Manager.Execute(query);

            if (_api.Manager.MethodSuccessed)
            {
                    return new NoteExtended(result.SelectNode("note"));
            }
            return null;
        }

        /// <summary>
        ///     Возращает список комментариев к заметке.
        /// </summary>
        /// <param name="noteId">ID заметки, комментарии которой нужно вернуть</param>
        /// <param name="ownerId">Идентификатор пользователя (по умолчанию - текущий пользователь).</param>
        /// <param name="sort">
        ///     Сортировка результатов (0 - по дате добавления в порядке возрастания, 1 - по дате добавления в
        ///     порядке убывания).
        /// </param>
        /// <param name="count">Количество комментариев, которое необходимо получить (не более 100). По умолчанию выставляется 20.</param>
        /// <param name="offset">Смещение, необходимое для выборки определенного подмножества комментариев.</param>
        public async Task<ListCount<Comment>> GetComments(int noteId,
                                                          int? ownerId = null,
                                                          SortOrder sort = null,
                                                          int? count = null,
                                                          int? offset = null)
        {
            var query = new ApiQuery("notes.getComments")
                .Add("note_id", noteId)
                .Add("owner_id", ownerId)
                .Add("sort", sort?.StringValue)
                .Add("count", count)
                .Add("offset", offset);

            var result = await _api.Manager.Execute(query);

            if (_api.Manager.MethodSuccessed)
            {
                var nodes = result.SelectNodes("items/*");
                if (nodes != null && nodes.Any())
                    return new ListCount<Comment>(result.Int("count").Value, nodes.Select(x => new Comment(x)).ToList());
            }
            return null;
        }

        /// <summary>
        ///     Возвращает список заметок, друзей пользователя.
        /// </summary>
        /// <param name="count">Количество сообщений, которое необходимо получить (но не более 100). По умолчанию выставляется 20.</param>
        /// <param name="offset">Смещение, необходимое для выборки определенного подмножества заметок.</param>
        public async Task<ListCount<Note>> GetFriendsNotes(int? count = null, int? offset = null)
        {
            var query = new ApiQuery("notes.getFriendsNotes")
                .Add("count", count)
                .Add("offset", offset);

            var result = await _api.Manager.Execute(query);

            if (_api.Manager.MethodSuccessed)
            {
                var nodes = result.SelectNodes("//note");
                if (nodes != null && nodes.Any())
                    return new ListCount<Note>(result.Int("count").Value, nodes.Select(x => new Note(x)).ToList());
            }
            return null;
        }

        /// <summary>
        ///     Восстанавливает удалённый комментарий.
        /// </summary>
        /// <param name="commentId">ID комментария, который нужно восстановить</param>
        /// <param name="ownerId">Идентификатор пользователя, владельца заметки (по умолчанию - текущий пользователь).</param>
        /// <returns>В случае успешного восстановления комментария метод возвратит true. </returns>
        public async Task<bool> RestoreComment(int commentId, int? ownerId = null)
        {
            var query = new ApiQuery("notes.restoreComment")
                .Add("comment_id", commentId)
                .Add("owner_id", ownerId);

            var result = await _api.Manager.Execute(query);

            return _api.Manager.MethodSuccessed && result.Bool().Value;
        }
    }
}